import glob
import json
import os

from docutils import nodes
from sphinx.util.docutils import SphinxDirective


def add_prop_attr_row(prop, attr, tbody, key=None):
    desc_row = nodes.row()
    tbody += desc_row
    desc_row += nodes.entry()
    desc_key_entry = nodes.entry()
    desc_row += desc_key_entry
    desc_key_entry += nodes.strong(text=key if key else attr)
    desc_text_entry = nodes.entry()
    desc_row += desc_text_entry
    desc_text_entry += nodes.paragraph(text=prop[attr])


def add_table_row(prop, tbody):
    # Create title / header row for this schema property.
    title_row = nodes.row()
    tbody += title_row
    title_entry = nodes.entry(morecols=2)
    title_row += title_entry
    title_entry += nodes.strong(text=prop['title'])
    # Add the property description, if available.
    if 'description' in prop:
        add_prop_attr_row(prop, 'description', tbody)
    # Add the property type, if available.
    if 'type' in prop:
        add_prop_attr_row(prop, 'type', tbody)
        if prop['type'] == 'array':
            # Process 'items' keyword with constraints on item types.
            if 'type' in prop['items']:
                add_prop_attr_row(prop['items'], 'type', tbody,
                                  key='entry type')
            if 'pattern' in prop['items']:
                add_prop_attr_row(prop['items'], 'pattern', tbody,
                                  key='entry regex match')
            if 'minLength' in prop['items']:
                add_prop_attr_row(prop['items'], 'minLength', tbody,
                                  key='minimum length for each entry')
    # Add type-specific parameters: 'minLength/pattern' for strings,
    if 'pattern' in prop:
        add_prop_attr_row(prop, 'pattern', tbody, key='regex match')
    if 'minLength' in prop:
        add_prop_attr_row(prop, 'minLength', tbody, key='minimum string length')
    # Add default value, if available.
    if 'default' in prop:
        add_prop_attr_row(prop, 'type', tbody, key='default value')


# Main Sphinx plugin
class RemoteAPIGen(SphinxDirective):
    def run(self):
        self.env.note_dependency(__file__)

        # List of documentation objects to return.
        new_doc = []

        # Get the JSONSchema API files to include.
        api_location = os.path.join(
            os.path.dirname(__file__),
            '..',
            '..',
            'siliconcompiler',
            'remote',
            'server_schema',
            'requests')
        api_schemas = glob.iglob(f'{api_location}/*.json')

        # Create a top-level section node to contain the individual API tables.
        top_section = nodes.section(ids=[nodes.make_id('server_top')])

        # Process each API individually, adding its doc components to the
        # array which we will return.
        for schema_file in api_schemas:
            with open(schema_file, 'r') as sf:
                api_schema = json.loads(sf.read())

            # Create a title heading for the section.
            title_str = api_schema['title'].strip('/')
            section = nodes.section(ids=[nodes.make_id(title_str)])
            section += nodes.title(text=title_str)

            # Create the request sub-section.
            req_section = nodes.section(ids=[nodes.make_id(f'{title_str}_request')])
            req_section += nodes.title(text='Request')
            # Add the endpoint's description.
            req_section += nodes.paragraph(text=api_schema['description'])
            # Add a table describing the schema.
            table = nodes.table()
            tgroup = nodes.tgroup(cols=3)
            tgroup += nodes.colspec(colwidth=10)
            tgroup += nodes.colspec(colwidth=50)
            tgroup += nodes.colspec(colwidth=100)
            table += tgroup
            tbody = nodes.tbody()
            tgroup += tbody
            for prop in api_schema['properties']:
                add_table_row(api_schema['properties'][prop], tbody)
            req_section += table

            # Create the response sub-section.
            resp_schema = os.path.join(
                os.path.dirname(__file__),
                '..',
                '..',
                'siliconcompiler',
                'remote',
                'server_schema',
                'responses',
                os.path.basename(schema_file),
            )
            resp_section = nodes.section(ids=[nodes.make_id(f'{title_str}_response')])
            resp_section += nodes.title(text='Response')
            # Add a table describing the response schema.
            if os.path.isfile(resp_schema):
                with open(resp_schema, 'r') as sf:
                    api_responses = json.loads(sf.read())
                resp_table = nodes.table()
                tgroup = nodes.tgroup(cols=3)
                tgroup += nodes.colspec(colwidth=10)
                tgroup += nodes.colspec(colwidth=50)
                tgroup += nodes.colspec(colwidth=100)
                resp_table += tgroup
                tbody = nodes.tbody()
                tgroup += tbody
                header_row = nodes.row()
                reason_entry = nodes.entry()
                reason_entry += nodes.strong(text='Reason')
                header_row += reason_entry
                stat_entry = nodes.entry()
                stat_entry += nodes.strong(text='Status Code')
                header_row += stat_entry
                resp_entry = nodes.entry()
                resp_entry += nodes.strong(text='Response Format')
                header_row += resp_entry
                tbody += header_row
                for resp in api_responses:
                    resp_row = nodes.row()
                    tbody += resp_row
                    reason = nodes.entry()
                    reason += nodes.strong(text=resp['reason'])
                    resp_row += reason
                    status_code = nodes.entry()
                    status_code += nodes.strong(text=resp['status_code'])
                    resp_row += status_code
                    fmt = nodes.entry()
                    json_text = json.dumps(resp['response_format'], indent=2)
                    fmt += nodes.literal_block(json_text, json_text)
                    resp_row += fmt
                resp_section += resp_table

            # Add request and response sections to the top-level API heading.
            section += req_section
            section += resp_section
            top_section += section

        # Done; return the array of document objects.
        new_doc += top_section
        return new_doc


def setup(app):
    app.add_directive('clientservergen', RemoteAPIGen)

    return {
        'version': '0.1',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }
